namespace System;
/// <summary>
/// DynamicMethodExecutor
/// </summary>
public sealed class DynamicMethodExecutor
{
    /// <summary>
    ///
    /// </summary>
    private readonly Func<object, object?[]?, object?> executor;
    /// <summary>
    /// 参数列表
    /// </summary>
    /// <returns></returns>
    public ParameterInfo[] Parameters { get; private set; } = Array.Empty<ParameterInfo>();
    /// <summary>
    ///
    /// </summary>
    /// <param name="methodInfo"></param>
    private DynamicMethodExecutor(MethodInfo methodInfo)
    {
        executor = GetExecuteDelegate(methodInfo);
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="methodInfo"></param>
    /// <returns></returns>
    public static DynamicMethodExecutor Compiled(MethodInfo methodInfo) => new DynamicMethodExecutor(methodInfo);

    /// <summary>
    ///
    /// </summary>
    /// <param name="instance"></param>
    /// <param name="parameters"></param>
    /// <returns></returns>
    public object? Execute(object instance, params object?[]? parameters) => executor?.Invoke(instance, parameters);

    /// <summary>
    ///
    /// </summary>
    /// <param name="methodInfo"></param>
    /// <returns></returns>
    private Func<object, object?[]?, object?> GetExecuteDelegate(MethodInfo methodInfo)
    {
        Parameters = (methodInfo ?? throw new ArgumentNullException(nameof(methodInfo))).GetParameters();

        ParameterExpression instanceParameter = Expression.Parameter(typeof(object), "instance");
        ParameterExpression parametersParameter = Expression.Parameter(typeof(object[]), "parameters");

        List<Expression> argumentExpressions = new List<Expression>();
        for (int i = 0; i < Parameters.Length; i++)
        {
            argumentExpressions.Add(Expression.Convert(
                Expression.ArrayIndex(
                parametersParameter, Expression.Constant(i)), Parameters[i].ParameterType));
        }

        MethodCallExpression methodCall = Expression.Call(
            methodInfo.IsStatic ? null : Expression.Convert(instanceParameter, methodInfo!.ReflectedType!),
            methodInfo,
            argumentExpressions);

        if (methodCall.Type == typeof(void))
        {
            Action<object, object?[]?> execute = Expression.Lambda<Action<object, object?[]?>>(
                    methodCall, instanceParameter, parametersParameter).Compile();

            return (instance, parameters) =>
            {
                execute(instance, parameters);
                return null;
            };
        }
        else
        {
            return Expression.Lambda<Func<object, object?[]?, object>>(
                    Expression.Convert(
                methodCall, typeof(object)), instanceParameter, parametersParameter).Compile();
        }
    }
}